package cn.jiangjiesheng.bootstrap.gateway.auth;

import com.google.common.base.CaseFormat;
import com.google.common.collect.Maps;
import cn.jiangjiesheng.bootstrap.commons.exception.AuthFailedException;
import cn.jiangjiesheng.bootstrap.commons.exception.QpaasOperationException;
import cn.jiangjiesheng.bootstrap.commons.response.ResponseWrapper;
import cn.jiangjiesheng.bootstrap.gateway.commons.CommonService;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 统一认证：mock或从Header、Cookie里取
 * <p>
 * Created by dev@jiangjiesheng.cn on 2021/8/5  <br/>
 */
@Slf4j
@Component
public class QPaasAuthFilter implements GlobalFilter, Ordered {

    private static final List<String> ACCESS_TOKEN_KEYS = Arrays.asList("access_token", "Authorization");

    @Autowired
    private QPaasAuthService qPaasAuthService;

    @Autowired
    private AuthFilterProperties authFilterProperties;

    @Autowired
    private CommonService commonService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.debug("===============QPaasAuthFilter====================");
        ServerHttpRequest req = exchange.getRequest();
        ServerHttpResponse resp = exchange.getResponse();
        DataBufferFactory bufferFactory = resp.bufferFactory();
        HttpHeaders headers = req.getHeaders();
        // 经判断，无需登录则直接返回
        if (commonService.isSkipAuth(exchange)) {
            log.debug("ignored auth validate for path:{}", req.getPath());
            return chain.filter(exchange);
        }
        try {
            // 组装传递给上游服务的Header
            ServerHttpRequest.Builder requestBuilder = req.mutate();
            // 从平台获取userInfo
            this.getUpstreamHeader(req, headers).forEach((key, value) -> {
                if (Objects.nonNull(value)) {
                    requestBuilder.header(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, key), value.toString());
                }
            });
            requestBuilder.header(HttpHeaders.ACCEPT_CHARSET, StandardCharsets.UTF_8.toString());
            // 修改request，添加用户信息
            ServerHttpRequest request = requestBuilder.build();
            return chain.filter(exchange.mutate().request(request).build());
        } catch (AuthFailedException e) {
            log.warn("用户认证失败 {}", headers);
            ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(resp) {
                @Override
                public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                    if (body instanceof Flux) {
                        Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                        return super.writeWith(fluxBody.map(dataBuffer -> {
                            String s = ResponseWrapper.builder().success(false)
                                    .result(e.getMessage())
                                    .message(e.getMessage())
                                    .build()
                                    .toString();
                            byte[] uppedContent = s.getBytes();
                            return bufferFactory.wrap(uppedContent);
                        }));
                    }
                    // if body is not a flux. never got there.
                    return super.writeWith(body);
                }
            };
            responseDecorator.setStatusCode(HttpStatus.UNAUTHORIZED);
            return chain.filter(exchange.mutate().response(responseDecorator).build());
        } catch (QpaasOperationException e) {
            log.warn("请求平台出错 {}", headers);
            ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(resp) {
                @Override
                public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                    if (body instanceof Flux) {
                        Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                        return super.writeWith(fluxBody.map(dataBuffer -> {
                            String s = ResponseWrapper.builder().success(false)
                                    .result(e.getMessage())
                                    .message(String.format("failed with request: %s", req.getHeaders()))
                                    .build()
                                    .toString();
                            byte[] uppedContent = s.getBytes();
                            return bufferFactory.wrap(uppedContent);
                        }));
                    }
                    // if body is not a flux. never got there.
                    return super.writeWith(body);
                }
            };
            responseDecorator.setStatusCode(HttpStatus.BAD_REQUEST);
            return chain.filter(exchange.mutate().response(responseDecorator).build());
        }
    }

    /**
     * 生成传给上游的header
     *
     * @param req
     * @param headers
     * @return
     */
    private Map<String, Object> getUpstreamHeader(ServerHttpRequest req, HttpHeaders headers) {
        Map<String, Object> returning = Maps.newHashMap();
        if (authFilterProperties.getMock().getEnabled()) {
            // 模拟登录(用于调试)
            returning = authFilterProperties.getMock().toMap();
            returning.put("accessToken", "mockedToken");
            log.info("using mock auth, mock info:{}", returning);
            return returning;
        } else {
            // 平台登录
            String accessToken = getAccessToken(req, headers);
            if (Objects.isNull(accessToken)) {
                return returning;
            }
            // 如果请求中存在access_token
            returning = qPaasAuthService.qPaasUserInfo(accessToken
                    .replace("Bearer ", "")
                    .trim());
        }
        log.debug("upstream headers for this request is {}", returning);
        return returning;
    }

    private String getAccessToken(ServerHttpRequest req, HttpHeaders headers) {
        Object accessToken = null;
        for (String accessTokenKey : ACCESS_TOKEN_KEYS) {
            accessToken = headers.get(accessTokenKey);
            // 如果header里没有，去cookie里找
            if (Objects.isNull(accessToken)) {
                if (Objects.nonNull(req.getCookies().get(accessTokenKey)) && !req.getCookies().get(accessTokenKey).isEmpty()) {
                    accessToken = req.getCookies().get(accessTokenKey).get(0);
                }
            }
            // 拿到Token之后就跳出循环
            if (Objects.nonNull(accessToken)) {
                break;
            }
        }
        if(Objects.isNull(accessToken)){
            accessToken = req.getQueryParams().get("access_token");
        }
        return Objects.isNull(accessToken) ? "" : accessToken.toString();
    }

    @Override
    public int getOrder() {
        return LOWEST_PRECEDENCE;
    }
}

